package at.fhooe.stefanirndorfer.labeling;

import static at.fhooe.stefanirndorfer.utilities.Utilities.E;
import static at.fhooe.stefanirndorfer.utilities.Utilities.N;
import static at.fhooe.stefanirndorfer.utilities.Utilities.NE;
import static at.fhooe.stefanirndorfer.utilities.Utilities.NW;
import static at.fhooe.stefanirndorfer.utilities.Utilities.S;
import static at.fhooe.stefanirndorfer.utilities.Utilities.SE;
import static at.fhooe.stefanirndorfer.utilities.Utilities.SW;
import static at.fhooe.stefanirndorfer.utilities.Utilities.W;

import java.util.ArrayList;
import java.util.Iterator;

import at.fhooe.stefanirndorfer.logging.Log;
import at.fhooe.stefanirndorfer.quadtree.QuadTreeNode;


public class Labeling_Manager {
	
	
	QuadTreeNode rootNode;
	ArrayList<Region[]> merges;
	
	public Labeling_Manager(){
		rootNode = null;
		merges = new ArrayList<Region[]>();
	}
	
	public void initialize(QuadTreeNode rootNode){
		this.rootNode = rootNode;
		label(rootNode);
	}
	
	/**
	 * assigns labels to node p and its sons
	 */
	public void label(QuadTreeNode p){
		// if is no leaf
		if(p.getChildNodes()[NW] != null){
			
			// label in order NW, NE, SW, SE
			label(p.getChildNodes()[NW]);
			label(p.getChildNodes()[NE]);
			label(p.getChildNodes()[SW]);
			label(p.getChildNodes()[SE]);
		} else {
			// if is a room leaf
			if(!p.isWall()){
				QuadTreeNode q = get_Equal_Adj_Neighbour(p, E);
				if(q != null){
					label_Adjacent(q, NW, SW, p);					
				}
				q = get_Equal_Adj_Neighbour(p, S);
				
				if(q != null){
					label_Adjacent(q, NW, NE, p);			
				}
				
				if(!p.isLabeled()){
					assign_New_Label(p);
				}
			}
			// else its a wall leaf
		}
	}
	

	/**
	 * @param p
	 */
	private void assign_New_Label(QuadTreeNode p) {
		Region r = new Region();
		p.setLabel(r);
		r.addLeaf(p);
	}
	
	/**
	 * Finds all descendants of node R adjacent to node P--1 e, in quadrants Q1 and Q2
	 * @param r
	 * @param q1
	 * @param q2
	 * @param p
	 */
	private void label_Adjacent(QuadTreeNode r, short q1, short q2,
			QuadTreeNode p) {
		if(r.getChildNodes()[NW] != null){
			label_Adjacent(r.getChildNodes()[q1], q1, q2, p);
			label_Adjacent(r.getChildNodes()[q2], q1, q2, p);
		}else{
			if(!r.isWall()){
				assign_Label(p,r);
			}
			//else would be a wall leaf
		}
		
	}

	/**
	 * Assign a label to nodes p and q if they do not 
	 * already have one. If both have different labels, then enter them in MERGES
	 * @param p
	 * @param r
	 */
	private void assign_Label(QuadTreeNode p, QuadTreeNode q) {
		if(p.isLabeled() && q.isLabeled()){
			if(!p.getLabel().getId().equals(q.getLabel().getId())){
				addToMergesArray(p,q);
			}
		}else{
			if(p.isLabeled()){
				q.setLabel(p.getLabel());
				q.getLabel().addLeaf(q);
			}else{
				if(q.isLabeled()){
					p.setLabel(q.getLabel());
					p.getLabel().addLeaf(p);
				}else{
					assign_New_Label(p);
					q.setLabel(p.getLabel());
					q.getLabel().addLeaf(q);
				}
			} 
		}
	}

	/**
	 * @param p
	 * @param q
	 */
	private void addToMergesArray(QuadTreeNode p, QuadTreeNode q) {
		
		
		String pLabelId = p.getLabel().getId();
		String qLabelId = q.getLabel().getId();
		
		for (int i = 0; i < merges.size(); i++) {
			String currMId0 = merges.get(i)[0].getId();
			String currMId1 = merges.get(i)[1].getId();
			if((currMId0.equals(pLabelId) && currMId1.equals(qLabelId))
					||
					(currMId1.equals(pLabelId) && currMId0.equals(qLabelId)))
				return;
		}
		merges.add(new Region[]{p.getLabel(), q.getLabel()});
		Log.info(this, "Length of merge-array: " + merges.size());		
	}

	/**
	 * Returns the neighbor of node P in horizontal or vertical direction D 
	 * which is greater than or equal in size to P. If such a node does 
	 * not exist, then a GRAY node of equal size is returned If this is also impossible, 
	 * then the node is adjacent to the border of the image and NULL is returned
	 * @param p
	 * @param direction
	 * @return
	 */
	private QuadTreeNode get_Equal_Adj_Neighbour(QuadTreeNode p, short direction) {
		QuadTreeNode q = null;
		
		if(p.getFather() != null && adj(direction, p.getSontype())){
				/* find a common ancestor */
				q = get_Equal_Adj_Neighbour(p.getFather(), direction);
		} else {
			q = p.getFather();
		}
		
		/* follow the reflected path back to locate the neighbor */
		if(q != null && q.getChildNodes()[0] != null){
			q = q.getChildNodes()[reflect(direction, p.getSontype())];
		}
		return q;
	}

	public void cleanUp_Merges(){
		
		pirntMergesArray(); //debug
		
		Iterator<Region[]> it = merges.iterator();
		
		while(it.hasNext()){
			Region[] currRegs = it.next();
			Region currSrcReg = currRegs[0];
			Region currDestReg = currRegs[1];
			
			//if(currDestReg.getLeafs().size() != 0)
			currSrcReg.moveRegionTo(currDestReg);
			
			it.remove();
			for (int i = 0; i < merges.size(); i++) {
				if(merges.get(i)[0].getId().equals(currSrcReg.getId())){
					merges.get(i)[0] = currDestReg;
				}else{
					if(merges.get(i)[1].getId().equals(currSrcReg.getId())){
						merges.get(i)[1] = currDestReg;
				}
				
			}
			}
		}
	}


	private void pirntMergesArray() {
		System.out.println("Merges Arrey to be cleand up: ");
		
		for (int i = 0; i < merges.size(); i++) {
			System.out.println("Region: "+ merges.get(i)[0].getId()+ " with: " + merges.get(i)[1].getId());
		}
		System.out.println("");
		}
		

//	/**
//	 * @param label
//	 * @param regToMerge
//	 */
//	private synchronized void moveLabelsToOtherRegion(Region label, Region regToMerge) {
//		ArrayList<QuadTreeNode> toMergeLeafs = regToMerge
//				.getLeafs();
//
//		Iterator<QuadTreeNode> mergeIt = toMergeLeafs.iterator();
//		while (mergeIt.hasNext()) {
//			mergeIt.next().setLabel(label);
//			mergeIt.remove();
//		}
//	}

	/**
	 * returns the quadrant (NW, NE ...) of a given quadrant mirrored over axis of param dir
	 * @param dir
	 * @param quadrant
	 * @return
	 */
	private short reflect(short dir, short quadrant){
		switch(dir){
		case (N):
			switch(quadrant){
			case NW:
				return SW; 
			case NE:
				return SE;
			case SE:
				return NE;
			case SW:
				return NW;
			}
			break;
			
		case E:
			switch(quadrant){
			case NW:
				return NE;
			case NE:
				return NW;
			case SE:
				return SW;
			case SW:
				return SE;
			}
			break;
		case S:
			switch(quadrant){
			case NW:
				return SW;
			case NE:
				return SE;
			case SE:
				return NE;
			case SW:
				return NW;
			}
			break;
			
		case W:
			switch(quadrant){
			case NW:
				return NE;
			case NE:
				return NW;
			case SE:
				return SW;
			case SW:
				return SE;
			}
			break;
		}
		// this should not be reached
		Log.error(this, "something wrong with the reflect-method");
		return -1;
	}
	
	/**
	 * ADJ(B, I) is true if and only if 
	 * childnode (quadrant) I is adjacent to side B of a node
	 * @param dir
	 * @param quadrant
	 * @return
	 */
	private boolean adj(short dir, short quadrant){
		
		switch(dir){
		case (N):
			switch(quadrant){
			case NW:
				return true; 
			case NE:
				return true;
			case SE:
				return false;
			case SW:
				return false;
			}
			break;
			
		case E:
			switch(quadrant){
			case NW:
				return false;
			case NE:
				return true;
			case SE:
				return true;
			case SW:
				return false;
			}
			break;
		case S:
			switch(quadrant){
			case NW:
				return false;
			case NE:
				return false;
			case SE:
				return true;
			case SW:
				return true;
			}
			break;
			
		case W:
			switch(quadrant){
			case NW:
				return true;
			case NE:
				return false;
			case SE:
				return false;
			case SW:
				return true;
			}
			break;
		}
		// this should not be reached
		Log.error(this, "something wrong with the adj-method");
		return false;
	}

	public void merge() {
		if(merges.size() >= 0){
			cleanUp_Merges();
		}else{
			Log.info(this, "nothing to merge!");
		}
	}

	public void relabel(QuadTreeNode n) {
		removeLabels(n);
		label(n);
		//visit northern and western borders of n
		visitBorder(N, n, NW, NE);
		visitBorder(W, n, NW, SW);
		visitBorder(S, n, SW, SE);
		visitBorder(E, n, NE, SE);
	}

	private void visitBorder(short dir, QuadTreeNode node, short sonType1, short sonType2) {
		if(node.getChildNodes()[NW] != null){
			visitBorder(dir, node.getChildNodes()[sonType1], sonType1, sonType2);
			visitBorder(dir, node.getChildNodes()[sonType2], sonType1, sonType2);
		}else{
			QuadTreeNode q = get_Equal_Adj_Neighbour(node, dir);
			if(q != null){
				assign_Label(node, q);
			}
		}	}

	

	private synchronized void removeLabels(QuadTreeNode n) {
		if(n.getChildNodes()[NW] != null){
			removeLabels(n.getChildNodes()[NW]);
			removeLabels(n.getChildNodes()[NE]);
			removeLabels(n.getChildNodes()[SW]);
			removeLabels(n.getChildNodes()[SE]);
		}
		
		if(n.isLabeled()){
			Region r = n.getLabel();
			r.removeLeaf(n);
			n.setLabel(null);
		}
	}

}
